feat: distinct, non-dismissible UI state for TOTP lockout#53
Conversation
verify_otp's per-account lockout (5 failed attempts/15min, backend PR authorizerdev/authorizer#670) returned the same generic dismissible error as a plain wrong code, so the form stayed submittable and users had no signal to stop retrying. Key off the new extensions.code (TOO_MANY_REQUESTS, requires @authorizerdev/authorizer-js with extensions.code support - authorizerdev/authorizer-js#39) to disable the OTP input and submit button and keep the message on screen instead of letting it be dismissed. No countdown: the backend doesn't expose remaining lockout duration, so this shows a static message rather than a timer that could be wrong.
- Hide "Resend OTP" when is_totp is true: TOTP codes are generated locally by an authenticator app on a 30s rolling window, never sent by the server, so offering to resend was misleading and the backend call for it doesn't apply to this flow. - Add autocomplete="one-time-code" to the OTP input so browsers/OS can offer native autofill for authenticator/SMS codes.
|
Added two more fixes to this branch (found while auditing this component, kept out of the initial commit until confirmed in scope):
Both verified live: signed in against a local backend with TOTP, confirmed via DOM inspection that the Resend OTP link is absent and the input carries |
If the server ever sends TOO_MANY_REQUESTS with an empty message, the form previously went completely silent: input and Submit both disabled, no explanation rendered at all (Message returns null for blank text). Added a fixed fallback string so the user always has some explanation for why the form just went dead, regardless of what the server sent.
|
Fixed the silent-frozen-form finding: added a fixed fallback message for when the TOO_MANY_REQUESTS error has no (or empty) message text, so the disabled form always has an explanation. |
|
Blocked on an authorizer-js release. This uses |
Summary
AuthorizerVerifyOtpnow detectserrors[0].code === 'TOO_MANY_REQUESTS'on a failedverify_otpand disables the OTP input + Submit button, and stops the error message from being dismissed - previously the lockout showed identically to a plain wrong code, so the form stayed submittable with no signal to stop retrying.Dependency (blocking merge)
This depends on
.codebeing available onauthorizer-jserrors: authorizerdev/authorizer-js#39 (not yet published to npm).package.jsonhere is intentionally untouched - once that PR is merged and published, bump@authorizerdev/authorizer-jsand this will typecheck/build cleanly. Until thenerrors[0]?.codetypechecks against the unpublished SDK shape only (verified locally vianpm link), not the currently-pinned3.2.1.Test plan
tsc --noEmitclean against the linked (unpublished)authorizer-jsbuildexample/app against a localauthorizerserver (branchsecurity/totp-hardening, includes theextensions.codebackend PR), signed up + enrolled TOTP, failedverify_otp6x through the real browser UI. Confirmed via DOM inspection: input and submit button bothdisabled: true, banner text "Too many failed attempts, please try again later", no dismiss icon.